
//
// 101206a- AH - Created.
// 101206b- AH - create(String, Tree).
// 101206c- AH - now implements new interface TreeCreator.
// 101220a- AH - Fixed that created roots did not show up in tree, a bit 
//               strange: In root case looks like I must communicate a structure
//               change event, but that did the trick.
// 101220b- AH - remove(Tree).
// 101225 - AH - treeChanged(Tree).
// 110614a- AH - valueForPathChanged(TreePath, Object).
// 110614b- AH - create(Tree).
// 110614c- AH - Fires change event from valueForPathChanged(TreePath, Object).
// 110617 - AH - (Bybanen *) Handle in valueForPathChanged that user has not 
//               entered data.
//

package org.soekrat.gui.swing.forest;

import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.tree.TreePath;

import org.soekrat.engine.forest.Forest;
import org.soekrat.engine.forest.Tree;
import org.soekrat.gui.swing.util.AbstractTreeModel;

/**
 * <p>
 *   Adapt a 
 *   {@link Forest} to be viewed in a 
 *   {@link JTree}.
 * </p>
 * @author Arne Halvorsen (AH)
 * @author Laetitia Le Chatton (LLC) 
 */
public final class ForestTreeModel extends AbstractTreeModel 
  implements TreeCreator
{
  /**
   * <p>
   *   The empty model shared.
   * </p>
   */
  public static final ForestTreeModel EMPTY=new ForestTreeModel();
  
  private final Forest m_forest;
  
  /**
   * <p>
   *   Creates the single empty shared model.
   * </p>
   */
  private ForestTreeModel(){ m_forest=null; }
  
  /**
   * <p>
   *   Constructor.
   * </p>
   * @param forest {@link Forest} to adapt.
   */
  public ForestTreeModel(Forest forest)
  {
    if (forest==null)
    {
      throw new NullPointerException("forest");
    }
    
    m_forest=forest;
  }

  // Specified in TreeModel.
  public Object getChild(Object parent, int idx)
  {
    if (parent==m_forest)
    {
      return m_forest.getTree(idx);
    }
    else
    {
      return ((Tree)parent).getChild(idx);
    }
  }

  // Specified in TreeModel.
  public int getChildCount(Object parent) 
  {
    if (parent==m_forest)
    {
      return m_forest.getTreeCount();
    }
    else
    {
      return ((Tree)parent).getChildCount();
    }
  }

  // Specified in TreeModel.
  public int getIndexOfChild(Object parent, Object child)
  {
    if (parent==m_forest)
    {
      return m_forest.getIndexOfTree((Tree)child);
    }
    else
    {
      return ((Tree)parent).getIndexOfChild((Tree)child);
    }
  }

  // Specified in TreeModel.
  public Object getRoot(){ return m_forest; }

  // Specified in TreeModel.
  public boolean isLeaf(Object node)
  {
    if (node==m_forest)
    {
      return m_forest.getTreeCount()==0;
    }
    else
    {
      return ((Tree)node).getChildCount()==0;
    }
  }
  
  // Specified in TreeModel.
  public void valueForPathChanged(TreePath path, Object val)
  {
    String txt=(val==null) ? "" : val.toString();
    txt=(txt==null) ? "" : txt.trim();
    if (txt.length()==0) return;
    
    Object last=path.getLastPathComponent();
    if (last instanceof Tree)
    {
      Tree tree=(Tree)last;
      tree.setSoekratName(val.toString());
      
      TreeModelEvent e=null;
      
      Tree parent=tree.getParent();
      if (parent==null)
      {
        int idx=m_forest.getIndexOfTree(tree);
        e=new TreeModelEvent(this, new Object[]{ m_forest }, new int[]{ idx }, 
          new Object[]{ tree });
      }
      else
      {
        int idx=parent.getIndexOfChild(tree);
        Object[] p=createPath(parent);
        e=new TreeModelEvent(this, p, new int[]{ idx }, 
          new Object[]{ tree });
      }
      
      fire(e, EventType.CHANGED);
    }
  }
  
  /**
   * <p>
   *   Creates
   *   {@link Tree} with initial name "New".
   * </p>
   * @param Parent, if {@code null} creates new root.
   * @return Created.
   * @throws IllegalArgumentException If {@code this} is the empty model. 
   */
  public Tree create(Tree parent){ return create("New", parent); }
 
  /**
   * <p>
   *   Creates 
   *   {@link Tree}.
   * </p>
   * @param name   Name of tree to create.
   * @param parent Parent, if {@code null} creates new root.
   * @return Created.
   * @throws IllegalArgumentException If {@code this} is the empty model.
   */
  public Tree create(String name, Tree parent)
  {
    if (this==EMPTY)
    {
      throw new IllegalArgumentException("empty model");  
    }
    
    if (name==null)
    {
      throw new NullPointerException("name");
    }
    
    Tree retVal;
    if (parent==null)
    {
      retVal=new Tree(name);
      int idx=m_forest.getTreeCount();
      m_forest.addTree(retVal);
      
      TreeModelEvent event=new TreeModelEvent(this, new Object[]{ m_forest }, 
        new int[]{ idx }, new Object[]{ retVal });
      
      fire(event, EventType.STRUCTURE);
    }
    else
    {
      int idx=parent.getChildCount();
      retVal=parent.createChild(name);
      Object[] path=createPath(parent);
      
      TreeModelEvent event=new TreeModelEvent(this, path, new int[]{ idx }, 
        new Object[]{ retVal });
        
      fire(event, EventType.INSERTED);
    }
    
    return retVal;
  }
  
  /**
   * <p>
   *   Removes tree.
   * </p>
   * @param tree {@link Tree} to remove.
   * @throws IllegalArgumentException If {@code this} is the empty model.
   * @throws IllegalArgumentException If {@code tree} is not in forest. 
   */
  public void remove(Tree tree)
  {
    if (this==EMPTY)
    {
      throw new IllegalArgumentException("empty model");  
    }
    if (tree==null)
    {
      throw new NullPointerException("tree");
    }
    
    Tree parent=tree.getParent();
    if (parent==null)
    {
      int idx=m_forest.getIndexOfTree(tree);
      if (idx==-1)
      {
        throw new IllegalArgumentException(
          "tree is root but not in this forest");
      }
      
      m_forest.removeTree(tree);
      
      TreeModelEvent event=new TreeModelEvent(this, new Object[]{ m_forest }, 
          new int[]{ idx }, new Object[]{ tree });
      fire(event, EventType.STRUCTURE);
    }
    else
    {
      int idx=parent.getIndexOfChild(tree);      
      parent.removeChild(tree);
      Object[] path=createPath(parent);
      
      Tree root=(Tree)path[1];
      if (!m_forest.containsTree(root))
      {
        throw new IllegalArgumentException(
          "tree is not node in any trees in this forest");
      }
      
      TreeModelEvent event=new TreeModelEvent(this, path, new int[]{ idx }, 
        new Object[]{ tree });
      fire(event, EventType.REMOVED);
    }
  }
  
  /**
   * <p>
   *   Fires change event to view to tell a 
   *   {@link Tree} has changed.
   * </p>
   * @param tree {@link Tree} that changed.
   */
  public void treeChanged(Tree tree)
  {
    if (tree==null)
    {
      throw new NullPointerException("tree");
    }
    
    Tree parent=tree.getParent();
    int idx=(parent==null) ? m_forest.getIndexOfTree(tree) 
                           : parent.getIndexOfChild(tree);
    
    TreeModelEvent e=new TreeModelEvent(this, createPath(parent), 
      new int[]{ idx }, new Object[]{ tree });
    
    fire(e, EventType.CHANGED);
  }
  
  private Object[] createPath(Tree node)
  {
    if (node==null) return new Object[]{ m_forest }; 
      
    Object[] nodePath=node.path();
    int n=nodePath.length;
    int np1=n+1;
    Object[] retVal=new Object[np1];
    retVal[0]=m_forest;
    for (int i=1, j=0; i<np1; i++, j++) retVal[i]=nodePath[j];
    return retVal;
  }
  
}
